/**
 * @author Rid King
 * @since 2017-05-19
 * @version 2.0.0
 * @description 数据观察者
 */

 function Observer(value) {
    this.value = value;
    this.walk(value);
  }
  Observer.prototype.walk = function(obj) {
    var keys = Object.keys(obj);
    for(var i = 0; i < keys.length; i++) {
      // 给所有属性添加 getter、setter
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  };
  
  var dep = [];
  dep.target = null;

function defineReactive(obj, key, val) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if(property && property.configurable === false) {
        return;
    }

    var getter = property && property.get;
    var setter = property && property.set;

    var childOb = observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            var value = getter ? getter.call(obj) : val;

            if(Dep.target) {
                dep.depend();
                if(childOb) {
                    childOb.dep.depend();
                }
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            var value = getter ? getter.call(obj) : val;
            if(newVal === value) {
                return;
            }
            if(setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            childOb = observe(newVal);
            dep.notify();
        }
    });
}

function observe(value) {
    if(!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
}

var Dep = function Dep() {
    this.subs = [];
};
Dep.prototype.addSub = function addSub(sub) {
    this.subs.push(sub);
};
Dep.prototype.depend = function depend() {
    if(Dep.target) {
        Dep.target.addDep(this);
    }
};
Dep.prototype.notify = function notify() {
    var subs = this.subs.slice();
    for(var i = 0; i < subs.length; i++) {
        subs[i].update();
    }
};

Dep.target = null;

var Watcher = function Watcher(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.value = this.get();
};
Watcher.prototype.get = function get() {
    Dep.target = this;
    var value = this.vm._data[this.exp];
    Dep.target = null;
    return value;
};
Watcher.prototype.addDep = function addDep(dep) {
    dep.addSub(this);
};
Watcher.prototype.update = function update() {
    this.run();
};
Watcher.prototype.run = function run() {
    this.cb.call(this.vm);
}